package edge

import (
	"errors"

	"github.com/twgh/xcgui/wapi"

	"syscall"
	"unsafe"
)

// IStream 允许读取和写入数据流对象。
//
// https://learn.microsoft.com/zh-cn/windows/win32/api/objidl/nn-objidl-istream
type IStream struct {
	Vtbl *IStreamVtbl
}

// NewStreamMem 创建内存流对象.
//
// data: 用于设置内存流的初始内容, 如果此参数为 nil，则返回的内存流没有任何初始内容。
func NewStreamMem(data []byte) (*IStream, error) {
	streamPtr := wapi.SHCreateMemStream(data)
	if streamPtr == 0 {
		return nil, errors.New("createMemStream failed")
	}
	return NewIStreamByPtr(streamPtr), nil
}

// NewStreamOnFileEx 打开或创建文件，并检索要读取或写入该文件的流。
//
// pszFile: 指定文件名。
//
// grfMode: 用于指定文件访问模式以及如何创建和删除公开流的对象的一个或多个 wapi.STGM 值。
//
// dwAttributes: 一个或多个标志值，用于在创建新文件时指定文件属性。wapi.FILE_ATTRIBUTE_, wapi.FILE_FLAG_ .
//
// fCreate: 可帮助与 grfMode 一起指定在创建流时应如何处理现有文件。
//
// pstmTemplate: 保留参数.
//
// https://learn.microsoft.com/zh-cn/windows/win32/api/shlwapi/nf-shlwapi-shcreatestreamonfileex
func NewStreamOnFileEx(pszFile string, grfMode wapi.STGM, dwAttributes uint32, fCreate bool, pstmTemplate ...uintptr) (*IStream, error) {
	streamPtr, err := wapi.SHCreateStreamOnFileEx(pszFile, grfMode, dwAttributes, fCreate, pstmTemplate...)
	if err != nil {
		return nil, err
	}
	if streamPtr == 0 {
		return nil, errors.New("createStreamOnFileEx failed")
	}
	return NewIStreamByPtr(streamPtr), nil
}

// NewIStreamByPtr 从 streamPtr 创建一个新的 IStream 实例。
//
// streamPtr: stream指针.
func NewIStreamByPtr(streamPtr uintptr) *IStream {
	return (*IStream)(unsafe.Pointer(streamPtr))
}

type IStreamVtbl struct {
	IUnknownVtbl
	Read         ComProc
	Write        ComProc
	Seek         ComProc
	SetSize      ComProc
	CopyTo       ComProc
	Commit       ComProc
	Revert       ComProc
	LockRegion   ComProc
	UnlockRegion ComProc
	Stat         ComProc
	Clone        ComProc
}

func (i *IStream) GetPtr() uintptr {
	return uintptr(unsafe.Pointer(i))
}

func (i *IStream) AddRef() uintptr {
	r, _, _ := i.Vtbl.AddRef.Call(uintptr(unsafe.Pointer(i)))
	return r
}

func (i *IStream) Release() uintptr {
	r, _, _ := i.Vtbl.Release.Call(uintptr(unsafe.Pointer(i)))
	return r
}

func (i *IStream) QueryInterface(refiid, object unsafe.Pointer) error {
	r, _, _ := i.Vtbl.QueryInterface.Call(uintptr(unsafe.Pointer(i)), uintptr(refiid), uintptr(object))
	if r != 0 {
		return syscall.Errno(r)
	}
	return nil
}

// Read 从当前搜寻指针开始，将指定数量的字节从流对象读取到内存中。
//
// buffer: 存储读取数据的缓冲区.
func (i *IStream) Read(buffer []byte) (int, error) {
	var bytesRead uint32
	hr, _, _ := i.Vtbl.Read.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(unsafe.Pointer(&buffer[0])),
		uintptr(len(buffer)),
		uintptr(unsafe.Pointer(&bytesRead)),
	)
	if hr != 0 {
		return 0, syscall.Errno(hr)
	}
	return int(bytesRead), nil
}

// Write 从当前搜寻指针处开始，将指定数量的字节写入流对象。
//
// buffer: 存储写入数据的缓冲区.
func (i *IStream) Write(buffer []byte) (int, error) {
	var bytesWritten uint32
	hr, _, _ := i.Vtbl.Write.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(unsafe.Pointer(&buffer[0])),
		uintptr(len(buffer)),
		uintptr(unsafe.Pointer(&bytesWritten)),
	)
	if hr != 0 {
		return 0, syscall.Errno(hr)
	}
	return int(bytesWritten), nil
}

// Clear 清空流对象中的所有数据, 全部填充为0, 查找指针将返回到流的开头。
func (i *IStream) Clear() error {
	info, err := i.Stat(STATFLAG_NONAME)
	if err != nil {
		return err
	}
	// 设置查找指针到流的开头
	_, err = i.Seek(0, STREAM_SEEK_SET)
	if err != nil {
		return err
	}
	// 写入空数据
	zeroData := make([]byte, info.CbSize)
	_, err = i.Write(zeroData)
	if err != nil {
		return err
	}
	// 设置查找指针到流的开头
	_, err = i.Seek(0, STREAM_SEEK_SET)
	if err != nil {
		return err
	}
	return nil
}

// STREAM_SEEK 表示流式数据中的定位方式
//
// https://learn.microsoft.com/zh-cn/windows/win32/api/objidl/ne-objidl-stream_seek
type STREAM_SEEK uint32

const (
	STREAM_SEEK_SET STREAM_SEEK = iota // 从流的起始位置开始
	STREAM_SEEK_CUR                    // 从当前指针位置开始
	STREAM_SEEK_END                    // 从流的末尾位置开始
)

// Seek 将查找指针更改为新位置。 新位置相对于流的开头、流的结束或当前查找指针。
//
// dlibMove: 要添加到 dwOrigin 参数指示的位置的位移。 如果 dwOrigin 是 STREAM_SEEK_SET，则会将其解释为无符号值，而不是有符号值。
//
// dwOrigin: dlibMove 中指定的位移的原点. STREAM_SEEK 常量.
func (i *IStream) Seek(dlibMove int64, dwOrigin STREAM_SEEK) (uint64, error) {
	var newPosition uint64
	hr, _, _ := i.Vtbl.Seek.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(dlibMove),
		uintptr(dwOrigin),
		uintptr(unsafe.Pointer(&newPosition)),
	)
	if hr != 0 {
		return 0, syscall.Errno(hr)
	}
	return newPosition, nil
}

// SetSize 更改流对象的大小。
//   - 调用此方法可预分配流的空间。
//   - 如果 libNewSize 参数大于当前流大小，则流将扩展到指示的大小，方法是使用未定义值的字节填充中间空间。
//   - 如果查找指针超过流的当前末尾，则此操作类似于 ISequentialStream：：Write 方法。
//   - 如果 libNewSize 参数小于当前流，则流将被截断为指示的大小。
//   - 搜寻指针不受流大小更改的影响。
//   - 调用 IStream：：SetSize 是获取大块连续空间的有效方法。
//
// libNewSize: 新大小.
func (i *IStream) SetSize(libNewSize uint64) error {
	hr, _, _ := i.Vtbl.SetSize.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(libNewSize),
	)
	if hr != 0 {
		return syscall.Errno(hr)
	}
	return nil
}

// CopyTo 将指定数量的字节从流中的当前搜寻指针复制到另一个流中的当前查找指针。
//
// pstm: 指向目标流的指针。 pstm 指向的流可以是新流或源流的克隆。
//
// count: 要从源流复制的字节数。
//
// 返回值: 实际复制的字节数。
func (i *IStream) CopyTo(pstm *IStream, count uint64) (uint64, error) {
	var bytesWritten uint64
	hr, _, _ := i.Vtbl.CopyTo.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(unsafe.Pointer(pstm)),
		uintptr(count),
		uintptr(0),
		uintptr(unsafe.Pointer(&bytesWritten)),
	)
	if hr != 0 {
		return 0, syscall.Errno(hr)
	}
	return bytesWritten, nil
}

// STGC 表示存储提交标志，用于控制存储行为的位掩码类型
//
// https://learn.microsoft.com/zh-cn/windows/win32/api/wtypes/ne-wtypes-stgc
type STGC uint32

const (
	// STGC_DEFAULT 默认存储行为，遵循标准提交规则
	STGC_DEFAULT STGC = 0

	// STGC_OVERWRITE 覆盖现有数据，即使存在版本冲突
	STGC_OVERWRITE STGC = 1

	// STGC_ONLYIFCURRENT 仅当对象当前未被修改时提交
	STGC_ONLYIFCURRENT STGC = 2

	// STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE
	// 仅提交到磁盘缓存（可能丢失数据），用于临时存储场景。
	// 注意：系统崩溃可能导致数据丢失，使用时必须明确风险
	STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE STGC = 4

	// STGC_CONSOLIDATE 合并存储空间，优化存储结构
	STGC_CONSOLIDATE STGC = 8
)

// Commit 方法可确保对在事务处理模式下打开的流对象所做的任何更改都反映在父存储中。
//   - 如果流对象在直接模式下打开， 则 Commit 除了将所有内存缓冲区刷新到下一级存储对象之外，没有其他效果。流的 COM 复合文件实现不支持在事务处理模式下打开流。
//
// grfCommitFlags: 控制提交对流对象的更改的方式: STGC 常量.
func (i *IStream) Commit(grfCommitFlags STGC) error {
	hr, _, _ := i.Vtbl.Commit.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(grfCommitFlags),
	)
	if hr != 0 {
		return syscall.Errno(hr)
	}
	return nil
}

// Revert 撤销自上次 Commit 以来的更改。
//   - 在直接模式下打开的流和使用 Revert 的 COM 复合文件实现的流上，此方法不起作用。
func (i *IStream) Revert() error {
	hr, _, _ := i.Vtbl.Revert.Call(
		uintptr(unsafe.Pointer(i)),
	)
	if hr != 0 {
		return syscall.Errno(hr)
	}
	return nil
}

// LOCK 表示资源锁定类型，用于控制并发访问的位掩码类型.
//
// https://learn.microsoft.com/zh-cn/windows/win32/api/objidl/ne-objidl-locktype
type LOCK uint32

const (
	// LOCK_WRITE 写锁定，允许并发读取但阻止其他写入
	// 注意：与 LOCK_EXCLUSIVE 同时使用时将升级为独占锁
	LOCK_WRITE LOCK = 1

	// LOCK_EXCLUSIVE 独占锁定，禁止其他所有访问
	// 使用该模式会完全阻塞资源，建议设置超时机制
	LOCK_EXCLUSIVE LOCK = 2

	// LOCK_ONLYONCE 一次性锁定，确保资源只被锁定一次
	// 适用于初始化保护场景，重复锁定将返回错误
	LOCK_ONLYONCE LOCK = 4
)

// LockRegion 限制对流中指定字节范围的访问。 支持此功能是可选的，因为某些文件系统不提供此功能。
//   - 必须先解锁该区域，然后才能释放流。
//
// offset: 指定范围开头的字节偏移量的整数。
//
// count: 指定要限制的范围长度的整数（以字节为单位）。
//
// lockType: 指定请求访问范围的限制, LOCK 常量。
func (i *IStream) LockRegion(offset uint64, count uint64, lockType LOCK) error {
	hr, _, _ := i.Vtbl.LockRegion.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(offset),
		uintptr(count),
		uintptr(lockType),
	)
	if hr != 0 {
		return syscall.Errno(hr)
	}
	return nil
}

// UnlockRegion 删除对以前使用 IStream.LockRegion 限制的字节范围的访问限制。
//   - 和之前锁定时的参数必须完全一致.
//   - 必须先解锁该区域，然后才能释放流。
//   - 无法单独锁定两个相邻区域，然后通过单个解锁调用解锁。
//
// offset: 指定范围开头的字节偏移量。
//
// count: 指定要限制的范围长度（以字节为单位）。
//
// lockType: 以前对范围施加的访问限制类型: LOCK 常量。
func (i *IStream) UnlockRegion(offset uint64, count uint64, lockType LOCK) error {
	hr, _, _ := i.Vtbl.UnlockRegion.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(offset),
		uintptr(count),
		uintptr(lockType),
	)
	if hr != 0 {
		return syscall.Errno(hr)
	}
	return nil
}

// STATSTG 存储统计信息结构，用于描述存储对象的属性和状态.
//
// https://learn.microsoft.com/zh-cn/windows/win32/api/objidl/ns-objidl-statstg
type STATSTG struct {
	// 对象名称指针 (OLE字符串)，使用后需调用 wapi.CoTaskMemFree 释放.
	//
	// 转换字符串方法：syscall.UTF16PtrToString(PwcsName)
	//
	// 若要不返回此成员，请在调用返回 STATSTG 结构的方法时指定 STATFLAG_NONAME 值.
	PwcsName *uint16

	// 对象类型标识，对应 STGTY 常量
	Type STGTY

	// 对象大小（以字节为单位）
	CbSize uint64

	// 最后修改时间，使用 syscall.Filetime 结构保存原始数据.
	//
	// 转换方法：time.Unix(0, Mtime.Nanoseconds()).UTC()
	Mtime syscall.Filetime

	// 创建时间，FILETIME 格式存储
	Ctime syscall.Filetime

	// 最后访问时间，FILETIME 格式存储
	Atime syscall.Filetime

	// 访问模式标志，对应 wapi.STGM 常量组合
	GrfMode wapi.STGM

	// 支持的锁类型，对应 LOCK 位掩码
	GrfLocksSupported LOCK

	// 类标识符（CLSID），128位全局唯一标识
	Clsid syscall.GUID

	// 状态位标志，用于对象持久化状态
	GrfStateBits uint32

	// 保留字段，必须初始化为0
	Reserved uint32
}

// STATFLAG 控制统计信息获取方式的枚举，决定返回数据的详细程度和操作行为.
//
// https://learn.microsoft.com/zh-cn/windows/win32/api/objidl/ns-objidl-statstg
type STATFLAG uint32

const (
	// STATFLAG_DEFAULT 默认获取模式，返回所有可用信息.
	//
	// 包含对象名称、完整属性和打开状态信息.
	STATFLAG_DEFAULT STATFLAG = 0

	// STATFLAG_NONAME 省略对象名称信息，提升性能.
	//
	// 使用此标志时，返回结构体的 pwcsName 字段将为nil.
	//
	// 注意：调用方不得尝试释放或访问名称字段.
	STATFLAG_NONAME STATFLAG = 1

	// STATFLAG_NOOPEN 以最小开销方式获取统计信息.
	//
	// 避免完全打开对象，可能返回部分元数据.
	//
	// 警告：可能影响返回数据的实时性.
	STATFLAG_NOOPEN STATFLAG = 2
)

// STGTY 表示存储对象类型枚举，用于标识不同的存储实体类型
//
// https://learn.microsoft.com/zh-cn/windows/win32/api/objidl/ne-objidl-stgty
type STGTY uint32

const (
	// STGTY_STORAGE 表示复合存储对象，可包含其他存储对象和流.
	//
	// 用于目录式的层次化存储结构，对应IStorage接口.
	STGTY_STORAGE STGTY = 1

	// STGTY_STREAM 表示字节流对象，用于存储序列化数据.
	//
	// 基本数据存储单元，对应IStream接口.
	STGTY_STREAM STGTY = 2

	// STGTY_LOCKBYTES 表示字节数组对象，支持原子访问.
	//
	// 用于低级别字节操作，对应ILockBytes接口.
	STGTY_LOCKBYTES STGTY = 3

	// STGTY_PROPERTY 表示属性存储对象，用于元数据管理.
	//
	// 存储键值对属性信息，需配合属性集使用.
	STGTY_PROPERTY STGTY = 4
)

// Stat 获取流的统计信息。
//
// grfStatFlag: 控制统计信息获取方式，决定返回数据的详细程度和操作行为. STATFLAG 常量.
func (i *IStream) Stat(grfStatFlag STATFLAG) (*STATSTG, error) {
	var stat STATSTG
	hr, _, _ := i.Vtbl.Stat.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(unsafe.Pointer(&stat)),
		uintptr(grfStatFlag),
	)
	if hr != 0 {
		return nil, syscall.Errno(hr)
	}
	return &stat, nil
}

// Clone 使用自己的 seek 指针创建新的流对象，该对象引用与原始流相同的字节。
//   - 创建一个新的流对象，用于访问相同的字节，但使用单独的查找指针。
//   - 新的流对象将看到与源流对象相同的数据。
//   - 写入一个对象的更改会立即显示在另一个对象中。 范围锁定在流对象之间共享。
//   - 克隆流实例中搜寻指针的初始设置与克隆操作时原始流中搜寻指针的当前设置相同。
func (i *IStream) Clone() (*IStream, error) {
	var clone *IStream
	hr, _, _ := i.Vtbl.Clone.Call(
		uintptr(unsafe.Pointer(i)),
		uintptr(unsafe.Pointer(&clone)),
	)
	if hr != 0 {
		return nil, syscall.Errno(hr)
	}
	return clone, nil
}

// GetBytes 获取流的内容。
func (i *IStream) GetBytes() ([]byte, error) {
	const bufferSize = 4096
	var content []byte
	for {
		buffer := make([]byte, bufferSize)
		n, hr := i.Read(buffer)
		if hr != nil && !errors.Is(hr, wapi.S_FALSE) {
			return nil, errors.New("stream read failed: " + hr.Error())
		}
		if n == 0 {
			break
		}
		content = append(content, buffer[:n]...)
	}
	return content, nil
}

// GetBytesAndRelease 获取流的内容并释放流。
func (i *IStream) GetBytesAndRelease() ([]byte, error) {
	defer i.Release()
	return i.GetBytes()
}
